package clidownloads

import (
        // standard lib
        "context"
        "fmt"
        "time"

        // kube
        "k8s.io/apimachinery/pkg/api/equality"
        "k8s.io/apimachinery/pkg/api/errors"
        apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        utilerrors "k8s.io/apimachinery/pkg/util/errors"
        "k8s.io/klog/v2"

        // openshift
        v1 "github.com/openshift/api/console/v1"
        operatorsv1 "github.com/openshift/api/operator/v1"
        "github.com/openshift/library-go/pkg/controller/factory"
        "github.com/openshift/library-go/pkg/operator/events"
        "github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
        "github.com/openshift/library-go/pkg/operator/v1helpers"
        "github.com/openshift/library-go/pkg/route/routeapihelpers"

        // informers
        consoleinformersv1 "github.com/openshift/client-go/console/informers/externalversions/console/v1"
        operatorinformersv1 "github.com/openshift/client-go/operator/informers/externalversions/operator/v1"
        routesinformersv1 "github.com/openshift/client-go/route/informers/externalversions/route/v1"

        // clients
        consoleclientv1 "github.com/openshift/client-go/console/clientset/versioned/typed/console/v1"
        operatorclientv1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
        routeclientv1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1"

        // operator
        "github.com/openshift/console-operator/pkg/api"
        controllersutil "github.com/openshift/console-operator/pkg/console/controllers/util"
        "github.com/openshift/console-operator/pkg/console/status"
        "github.com/openshift/console-operator/pkg/console/subresource/util"
)

type CLIDownloadsSyncController struct {
        // clients
        operatorClient            v1helpers.OperatorClient
        consoleCliDownloadsClient consoleclientv1.ConsoleCLIDownloadInterface
        routeClient               routeclientv1.RoutesGetter
        operatorConfigClient      operatorclientv1.ConsoleInterface
}

func NewCLIDownloadsSyncController(
        // clients
        operatorClient v1helpers.OperatorClient,
        operatorConfigClient operatorclientv1.OperatorV1Interface,
        cliDownloadsInterface consoleclientv1.ConsoleCLIDownloadInterface,
        routeClient routeclientv1.RoutesGetter,
        // informers
        operatorConfigInformer operatorinformersv1.ConsoleInformer,
        consoleCLIDownloadsInformers consoleinformersv1.ConsoleCLIDownloadInformer,
        routeInformer routesinformersv1.RouteInformer,
        // events
        recorder events.Recorder,
) factory.Controller {

        ctrl := &CLIDownloadsSyncController{
                // clients
                operatorClient:            operatorClient,
                consoleCliDownloadsClient: cliDownloadsInterface,
                routeClient:               routeClient,
                operatorConfigClient:      operatorConfigClient.Consoles(),
        }

        return factory.New().
                WithFilteredEventsInformers( // configs
                        controllersutil.IncludeNamesFilter(api.ConfigResourceName),
                        operatorConfigInformer.Informer(),
                ).WithFilteredEventsInformers( // console resources
                controllersutil.IncludeNamesFilter(api.OpenShiftConsoleDownloadsRouteName),
                routeInformer.Informer(),
        ).WithInformers(
                consoleCLIDownloadsInformers.Informer(),
        ).ResyncEvery(time.Minute).WithSync(ctrl.Sync).
                ToController("ConsoleCLIDownloadsController", recorder.WithComponentSuffix("console-cli-downloads-controller"))
}

func (c *CLIDownloadsSyncController) Sync(ctx context.Context, controllerContext factory.SyncContext) error {
        operatorConfig, err := c.operatorConfigClient.Get(ctx, api.ConfigResourceName, metav1.GetOptions{})
        if err != nil {
                return err
        }
        updatedOperatorConfig := operatorConfig.DeepCopy()

        switch updatedOperatorConfig.Spec.ManagementState {
        case operatorsv1.Managed:
                klog.V(4).Infoln("console is in a managed state: syncing ConsoleCliDownloads custom resources")
        case operatorsv1.Unmanaged:
                klog.V(4).Infoln("console is in an unmanaged state: skipping ConsoleCliDownloads custom resources sync")
                return nil
        case operatorsv1.Removed:
                klog.V(4).Infoln("console is in a removed state: deleting ConsoleCliDownloads custom resources")
                return c.removeCLIDownloads(ctx)
        default:
                return fmt.Errorf("console is in an unknown state: %v", updatedOperatorConfig.Spec.ManagementState)
        }

        downloadsRoute, downloadsRouteErr := c.routeClient.Routes(api.TargetNamespace).Get(ctx, api.OpenShiftConsoleDownloadsRouteName, metav1.GetOptions{})
        if downloadsRouteErr != nil {
                return downloadsRouteErr
        }

        downloadsURI, _, downloadsRouteErr := routeapihelpers.IngressURI(downloadsRoute, downloadsRoute.Spec.Host)
        if downloadsRouteErr != nil {
                return downloadsRouteErr
        }

        statusHandler := status.NewStatusHandler(c.operatorClient)
        ocConsoleCLIDownloads := PlatformBasedOCConsoleCLIDownloads(downloadsURI.String(), api.OCCLIDownloadsCustomResourceName)
        _, ocCLIDownloadsErrReason, ocCLIDownloadsErr := ApplyCLIDownloads(ctx, c.consoleCliDownloadsClient, ocConsoleCLIDownloads)
        statusHandler.AddCondition(status.HandleDegraded("OCDownloadsSync", ocCLIDownloadsErrReason, ocCLIDownloadsErr))
        if ocCLIDownloadsErr != nil {
                return statusHandler.FlushAndReturn(ocCLIDownloadsErr)
        }

        _, odoCLIDownloadsErrReason, odoCLIDownloadsErr := ApplyCLIDownloads(ctx, c.consoleCliDownloadsClient, ODOConsoleCLIDownloads(downloadsURI.String()))
        statusHandler.AddCondition(status.HandleDegraded("ODODownloadsSync", odoCLIDownloadsErrReason, odoCLIDownloadsErr))
        if odoCLIDownloadsErr != nil {
                return statusHandler.FlushAndReturn(odoCLIDownloadsErr)
        }

        _, helmCLIDownloadsErrReason, helmCLIDownloadsErr := ApplyCLIDownloads(ctx, c.consoleCliDownloadsClient, HELMConsoleCLIDownloads(downloadsURI.String()))
        statusHandler.AddCondition(status.HandleDegraded("HELMDownloadsSync", helmCLIDownloadsErrReason, helmCLIDownloadsErr))
        if helmCLIDownloadsErr != nil {
                return statusHandler.FlushAndReturn(helmCLIDownloadsErr)
        }

        return statusHandler.FlushAndReturn(nil)
}

func (c *CLIDownloadsSyncController) removeCLIDownloads(ctx context.Context) error {
        defer klog.V(4).Info("finished deleting ConsoleCliDownloads custom resources")
        var errs []error
        errs = append(errs, c.consoleCliDownloadsClient.Delete(ctx, api.OCCLIDownloadsCustomResourceName, metav1.DeleteOptions{}))
        errs = append(errs, c.consoleCliDownloadsClient.Delete(ctx, api.ODOCLIDownloadsCustomResourceName, metav1.DeleteOptions{}))
        errs = append(errs, c.consoleCliDownloadsClient.Delete(ctx, api.HELMCLIDownloadsCustomResourceName, metav1.DeleteOptions{}))
        return utilerrors.FilterOut(utilerrors.NewAggregate(errs), errors.IsNotFound)
}

func GetPlatformURL(baseURL, platform, archiveType string) string {
        return fmt.Sprintf("%s/%s/%s", baseURL, platform, archiveType)
}

func PlatformBasedOCConsoleCLIDownloads(host, cliDownloadsName string) *v1.ConsoleCLIDownload {
        baseURL := fmt.Sprintf("%s", util.HTTPS(host))
        platforms := []struct {
                label    string
                key      string
                archType string
        }{
                {"Linux x86_64", "amd64/linux", "oc.tar"},
                {"Mac x86_64", "amd64/mac", "oc.zip"},
                {"Windows x86_64", "amd64/windows", "oc.zip"},
                {"Linux ARM 64", "arm64/linux", "oc.tar"},
                {"Mac ARM 64", "arm64/mac", "oc.zip"},
                {"Linux LoongArch 64", "loong64/linux", "oc.tar"},
                {"Linux SW 64", "sw64/linux", "oc.tar"},
        }

        links := []v1.CLIDownloadLink{}
        for _, platform := range platforms {
                links = append(links, v1.CLIDownloadLink{
                        Href: GetPlatformURL(baseURL, platform.key, platform.archType),
                        Text: fmt.Sprintf("下载 oc 用于 %s", platform.label),
                })
        }

        /* links = append(links, v1.CLIDownloadLink{
                Href: fmt.Sprintf("%s/oc-license", baseURL),
                Text: "LICENSE",
        }) */

        return &v1.ConsoleCLIDownload{
                ObjectMeta: metav1.ObjectMeta{
                        Name: cliDownloadsName,
                },
                Spec: v1.ConsoleCLIDownloadSpec{
                        Description: `使用 Uccps 命令行界面，您可以从终端创建应用程序和管理 Uccps 项目。

oc 二进制文件提供与 kubectl 二进制文件相同的功能，但它进一步扩展以原生支持统信容器云平台功能。
`,
                        DisplayName: "oc - Uccps 命令行接口 (CLI)",
                        Links:       links,
                },
        }
}

func ODOConsoleCLIDownloads(host string) *v1.ConsoleCLIDownload {
        baseURL := fmt.Sprintf("%s", util.HTTPS(host))
        platforms := []struct {
                label    string
                key      string
                archType string
        }{
                {"Linux x86_64", "amd64/linux", "odo.tar"},
                {"Linux ARM 64", "arm64/linux", "odo.tar"},
                {"Linux LoongArch 64", "loong64/linux", "odo.tar"},
                {"Linux SW 64", "sw64/linux", "odo.tar"},
        }

        links := []v1.CLIDownloadLink{}
        for _, platform := range platforms {
                links = append(links, v1.CLIDownloadLink{
                        Href: GetPlatformURL(baseURL, platform.key, platform.archType),
                        Text: fmt.Sprintf("下载 odo 用于 %s", platform.label),
                })
        }

        return &v1.ConsoleCLIDownload{
                ObjectMeta: metav1.ObjectMeta{
                        Name: api.ODOCLIDownloadsCustomResourceName,
                },
                Spec: v1.ConsoleCLIDownloadSpec{
                        Description: `odo 是一种快速、迭代和直接的 CLI 工具，适用于在 Uccps 上编写、构建和部署应用程序的开发人员。

odo 抽象出复杂的 Kubernetes 和 Uccps 概念，从而使开发人员能够专注于对他们来说最重要的事情：代码。
`,
                        DisplayName: "odo - Uccps 开发人员命令行接口 (CLI)",
                        Links: links,
                },
        }
}

func HELMConsoleCLIDownloads(host string) *v1.ConsoleCLIDownload {
        baseURL := fmt.Sprintf("%s", util.HTTPS(host))
        platforms := []struct {
                label    string
                key      string
                archType string
        }{
                {"Linux x86_64", "amd64/linux", "helm.tar"},
                {"Linux ARM 64", "arm64/linux", "helm.tar"},
                {"Linux LoongArch 64", "loong64/linux", "helm.tar"},
                {"Linux SW 64", "sw64/linux", "helm.tar"},
        }

        links := []v1.CLIDownloadLink{}
        for _, platform := range platforms {
                links = append(links, v1.CLIDownloadLink{
                        Href: GetPlatformURL(baseURL, platform.key, platform.archType),
                        Text: fmt.Sprintf("下载 helm 用于 %s", platform.label),
                })
        }

        return &v1.ConsoleCLIDownload{
                ObjectMeta: metav1.ObjectMeta{
                        Name: api.HELMCLIDownloadsCustomResourceName,
                },
                Spec: v1.ConsoleCLIDownloadSpec{
                        Description: `Helm 3 是 Kubernetes 应用程序的包管理器，它支持定义、安装和升级打包为 Helm Charts 的应用程序。
`,
                        DisplayName: "helm - Helm 3 命令行接口 (CLI)",
                        Links: links,
                },
        }
}

// TODO: All the custom `Apply*` functions should be at some point be placed into:
// openshift/library-go/pkg/console/resource/resourceapply/core.go
func ApplyCLIDownloads(ctx context.Context, consoleClient consoleclientv1.ConsoleCLIDownloadInterface, requiredCLIDownloads *v1.ConsoleCLIDownload) (*v1.ConsoleCLIDownload, string, error) {
        cliDownloadsName := requiredCLIDownloads.ObjectMeta.Name
        existingCLIDownloads, err := consoleClient.Get(ctx, cliDownloadsName, metav1.GetOptions{})
        existingCLIDownloadsCopy := existingCLIDownloads.DeepCopy()
        if apierrors.IsNotFound(err) {
                actualCLIDownloads, err := consoleClient.Create(ctx, requiredCLIDownloads, metav1.CreateOptions{})
                if err != nil {
                        klog.V(4).Infof("error creating %s consoleclidownloads custom resource: %s", cliDownloadsName, err)
                        return nil, "FailedCreate", err
                }
                klog.V(4).Infof("%s consoleclidownloads custom resource created", cliDownloadsName)
                return actualCLIDownloads, "", nil
        }
        if err != nil {
                klog.V(4).Infof("error getting %s custom resource: %v", cliDownloadsName, err)
                return nil, "", err
        }
        specSame := equality.Semantic.DeepEqual(existingCLIDownloadsCopy.Spec, requiredCLIDownloads.Spec)
        modified := resourcemerge.BoolPtr(false)
        resourcemerge.EnsureObjectMeta(modified, &existingCLIDownloadsCopy.ObjectMeta, requiredCLIDownloads.ObjectMeta)
        if specSame && !*modified {
                klog.V(4).Infof("%s consoleclidownloads custom resource exists and is in the correct state", cliDownloadsName)
                return existingCLIDownloadsCopy, "", nil
        }

        existingCLIDownloadsCopy.Spec = requiredCLIDownloads.Spec
        actualCLIDownloads, err := consoleClient.Update(ctx, existingCLIDownloadsCopy, metav1.UpdateOptions{})
        if err != nil {
                klog.V(4).Infof("error updating %s consoleclidownloads custom resource: %v", cliDownloadsName, err)
                return nil, "FailedUpdate", err
        }
        return actualCLIDownloads, "", nil
}
